/*
 * Copyright (c) 2010 Mathew Hall, University of Sheffield.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution.
 *
 * Neither the name of the University of Sheffield nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package primitives.cluster;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import primitives.graph.Node;

/**
 * Represents a Node in the cluster tree - parent to multiple ICluserLevels.
 *
 * @author mat
 */
public class ClusterNode implements IClusterLevel, Serializable {
    //XXX:  THIS IS A BAD IDEA! CHANGED SERIAL VERSION UID: IF THINGS BREAK UNDO THIS CHANGE!

    private static final long serialVersionUID = -4427232406185809849L; //-232349763786286L;
    protected Set<IClusterLevel> clusters;
    protected Set<Integer> id;
    private int size;

    public ClusterNode() {
        clusters = new HashSet<IClusterLevel>();
        id = new HashSet<Integer>();
        size = -1;
    }

    public ClusterNode(Set<Node> n) {
        this();

        for (Node node : n) {
            addNode(node);
        }
    }

	/**
	 * Calculate the number of nodes below this cluster in the tree.
	 * @return the number of nodes contained in clusters below this one.
	 */
    public int getSize() {

        if (size == - 1) {
            int ret = 0;

            for (IClusterLevel c : clusters) {

                if (c == this) {
                    Logger.getLogger(ClusterNode.class.getName()).log(Level.WARNING, "Circular reference in cluster tree (getSize)");
                } else {
                    ret += c.getSize();
                }

            }
            size = ret;
        }

        return size;
    }

    public void setChildren(Set<IClusterLevel> c) {
        clusters.addAll(c);
        size = -1;
    }

    public void addChild(IClusterLevel c) {
        clusters.add(c);
        size = -1;
    }

    public ClusterNode deepCopy() {
        ClusterNode newMe = new ClusterNode();
        for (IClusterLevel c : clusters) {
            newMe.addChild(c.deepCopy());
        }
        for (Integer i : id) {
            newMe.setID(i);
        }
        return newMe;
    }

    /**
     * *
     * Destructive!
     *
     * @param c
     */
    public void deleteChild(IClusterLevel c) {
        if (clusters.remove(c)) {
            return;
        }
        for (IClusterLevel cl : clusters) {
            if (cl == c) {
                Logger.getLogger(ClusterNode.class.getName()).log(Level.WARNING, "Search term not deleted (deleteChild)", c);
            }
            if (cl.encapsulates() && this != cl) {
                ((ClusterNode) cl).deleteChild(c);
            }
        }
        size = -1;
    }

	/**
	 * Recursively find and return all leaf nodes beneath this cluster
	 * @return every leaf node that this cluster and its children contains
	 */
    public List<ClusterLeaf> getLeaves() {
        ArrayList<ClusterLeaf> ret = new ArrayList<ClusterLeaf>();

        for (IClusterLevel c : clusters) {
            if (c.encapsulates()) {
                ret.addAll(c.getLeaves());
            } else {
                ret.add((ClusterLeaf) c);
            }
        }
        return ret;
    }

    @Override
    public String toString() {
        return "\nClusterNode  [clusters=\n\t" + clusters + "\n]";
    }

    @Override
    public void addNode(Node n) {

        for (IClusterLevel c : clusters) {
            //Just put n in the first child cluster
            if (c.getSize() == 0) {
                c.addNode(n);
                return;
            }
        }
        ClusterLeaf l = new ClusterLeaf();
        clusters.add(l);
        l.addNode(n);


        size = -1;
    }

    public Set<Integer> getIDs() {
        if (id.contains((-1))) {
            Logger.getLogger(ClusterNode.class.getName()).log(Level.WARNING, "ID with -1 in list of ClusterNode IDs");
        }
        //HashSet<Integer> copy = (HashSet<Integer>)id;
        return id; //(Set<Integer>)(copy.clone());
    }

    @Override
    public boolean connectivityBidir(Node n) {
        if (clusters.isEmpty()) {
            return false;
        }
        for (IClusterLevel c : clusters) {
            if (c.connectivityBidir(n)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean connectivityFrom(Node n) {
        if (clusters.isEmpty()) {
            return false;
        }
        for (IClusterLevel c : clusters) {
            if (c.connectivityFrom(n)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean connectivityTo(Node n) {
        if (clusters.isEmpty()) {
            return false;
        }
        for (IClusterLevel c : clusters) {
            if (c.connectivityTo(n)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean contains(Node n) {
        if (clusters.isEmpty()) {
            return false;
        }
        for (IClusterLevel c : clusters) {
            if (c.contains(n)) {
                return true;
            }
        }
        return false;
    }

    public boolean knownAs(int i) {
        return id.contains(i);
    }

	/**
	 * Get the set of all nodes beneath this cluster in the tree.
	 * @return all nodes in this branch of the tree
	 */
    @Override
    public Set<Node> getNodes() {
        Set<Node> n = new HashSet<Node>(100, 0.6f);
        for (IClusterLevel c : clusters) {
            c.getNodes(n);
        }
        return n;
    }

	
    public void getNodes(Set<Node> to) {
        for (IClusterLevel c : clusters) {
            c.getNodes(to);
        }
    }

	/**
	 * Get only the nodes contained in this cluster's leaves
	 * @return all nodes stored in leaves of this cluster.
	 */
    public Set<Node> getNodesInLeaves() {
        Set<Node> n = new HashSet<Node>();
        for (IClusterLevel c : clusters) {
            if (!c.encapsulates()) {
                n.addAll(c.getNodes());
            }
        }
        return n;
    }

    public boolean encapsulates() {
        return true;
    }

	@Deprecated
    public ClusterLeaf getCluster() {
        ClusterLeaf n = new ClusterLeaf();
        for (Node node : getNodes()) {
            n.addNode(node);
        }
        return n;
    }

	/**
	 * Get the immediate children of this cluster
	 */
    public Set<IClusterLevel> getChildren() {
        //HashSet<IClusterLevel> ret = new HashSet<IClusterLevel>(clusters);
        //return ret;
        HashSet<IClusterLevel> cast = (HashSet<IClusterLevel>) clusters;
        return cast;
    }

    public void setID(int i) {
        id.add(i);
    }
}
